package com.ddch.tinkerlib;

import android.content.Context;

import com.ddch.tinkerlib.utils.ArrayUtils;
import com.ddch.tinkerlib.utils.Constants;
import com.ddch.tinkerlib.utils.ReflectUtils;

import java.io.File;
import java.util.HashSet;

import dalvik.system.DexClassLoader;
import dalvik.system.PathClassLoader;

/**
 * created by 韦敏敏
 * on 2019/12/9
 *
 * 参考博客：Android 类加载机制以及基于类加载机制的热修复  https://www.jianshu.com/p/0fe079735230
 *
 */
public class FixDexUtils {
    // 存放需要修复的dex集合，可能不止一个
    private static HashSet<File> loadedDex = new HashSet<>();
    static {
        //修复前进行清理工作
        loadedDex.clear();
    }

    /**
     * 加载热修复的dex文件
     * @param context
     */
    public static void loadFixDex(Context context){
        if(context == null)
            return;
        File fileDir = context.getDir(Constants.DEX_DIR, Context.MODE_PRIVATE);
        File[] listFiles = fileDir.listFiles();
        for(File file : listFiles){
            if(file.getName().endsWith(Constants.DEX_SUFFIX) && !Constants.DEX_MAIN.equals(file.getName())){
                loadedDex.add(file);
            }
        }
        // 模拟类加载器
        createDexClassLoader(context, fileDir);
    }

    /**
     * 创建加载补丁的DexClassLoader (自有)
     * @param context
     * @param fileDir
     */
    private static void createDexClassLoader(Context context, File fileDir) {
        // 创建临时解压目录（先解压到该目录，再加载java）
        String optimizedDir = fileDir.getAbsolutePath() + File.separator + "opt_dex";
        // 不存在就创建
        File fopt = new File(optimizedDir);
        if(!fopt.exists()){
            // 创建多级目录
            fopt.mkdirs();
        }
        for(File dex : loadedDex){
            // 加载任意位置的 dex/zip/apk/jar
            // 每遍历一个要修复的dex文件，就需要插桩一次
            // 参数：DexClassLoader( dex包路径、解压目录、寻找本地库、类加载器)
            DexClassLoader classLoader = new DexClassLoader(dex.getAbsolutePath(), optimizedDir, null, context.getClassLoader());
            hotFix(classLoader, context);
        }



    }

    /**
     * 热修复
     * @param classLoader 自有的类加载器，加载了修复包的DexClassLoader
     * @param context  上下文
     */
    private static void hotFix(DexClassLoader classLoader, Context context) {
        // 获取系统的PathClassLoader  加载已经安装的 Apk(/data/app/package)的 Apk 文件
        PathClassLoader pathLoader = (PathClassLoader) context.getClassLoader();
        try {
            // 获取自有的dexElements数组对象
            Object myDexElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(classLoader));
            // 获取系统的dexElements数组对象
            Object systemDexElements = ReflectUtils.getDexElements(ReflectUtils.getPathList(pathLoader));
            // 合并成新的dexElements数组对象
            Object dexElements = ArrayUtils.combineArray(myDexElements, systemDexElements);
            /**
             *重新赋值给系统的pathList属性 ----- 修改pathList中的dexElements对象
             * 通过反射获取系统的pathList对象
             */
            Object systemPathList = ReflectUtils.getPathList(pathLoader);
            ReflectUtils.setField(systemPathList, systemPathList.getClass(), dexElements);
        } catch (Exception e) {
            e.printStackTrace();
        }


    }

}
